/**
 * MessageFetcher is a handler of HTTP reqeust/respone
 * to/from Chocolate server.
 */
package edu.pku.se.chocolate.solver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import java.util.Collection;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import android.sax.Element;
import android.sax.ElementListener;
import android.sax.EndElementListener;
import android.sax.EndTextElementListener;
import android.sax.RootElement;
import android.sax.StartElementListener;
import android.util.Log;
import android.util.Xml;

import edu.pku.se.chocolate.datastructure.Category;
import edu.pku.se.chocolate.datastructure.Message;
import edu.pku.se.chocolate.datastructure.MessageNumberResponse;
import edu.pku.se.chocolate.datastructure.RequstMessageResponse;
import edu.pku.se.chocolate.datastructure.Tag;

/**
 * @author Zhiyou Liu
 *
 */
public class MessageFetcher {

	private static final Exception WrongParamException = null;
	private static final String TAG = "MessageFetcher";
	private String serverAddress;
	/**
	 * + Construct MessageFetcher using the default server address
	 */
	public MessageFetcher() {
		//serverAddress = "http://ourserver.is.not.here.com/";
		//serverAddress = "http://115.27.22.114:8000/bdwmtest/";
//		serverAddress = "http://162.105.16.238:8080/chocolate/";
//		serverAddress = "http://chocolate.seemsfunny.com/chocolate/";
		Log.i(TAG, "MessageFetcher constructed");
	}
	/**
	 * Construct MessageFetcher using `servAddresss' as
	 * server address.
	 * @param servAddress server address this fetcher uses.
	 */
	public MessageFetcher(String servAddress) {
		serverAddress = servAddress;
		Log.i(TAG, "MessageFetcher constructed with "+ servAddress);
	}
	
	/**
	 * Request messages from server. 
	 * @param tag requesting message belong to `tag'
	 * @param ID returning message's ID will not larger than `ID'
	 * @param N returning messages will not more than `N'
	 * @return a list of messages matches limitation.
	 * @throws Exception 
	 * @throws SAXException 
	 */
	public RequstMessageResponse getMessage(Tag tag, int ID, int N) throws SAXException, Exception {
		String tagString = formatQueryTag(tag);
		List<NameValuePair> data = new ArrayList<NameValuePair>(3);
		data.add(new BasicNameValuePair("tag", tagString));
		data.add(new BasicNameValuePair("ID", String.valueOf(ID)));
		data.add(new BasicNameValuePair("N", String.valueOf(N)));
		//InputStream is = postData(data, serverAddress + "requestMessage");
//		InputStream is = postData(data, serverAddress + "lucene");
		InputStream is = postData(data, ChocolateSolver.getInstance().getServerAddress() + "lucene");
		return parseMessageResponse(is, tag, ID, N);
	}
	
	// Here is a hack for inner class+final type
	private class DataWarpper {
		public int msgID;
		public int cnout;
		public String msgSummary;
		public String msgTitle;
		public String msgLink;
		public String msgTime;
		public String msgReturnTag;
	}
	public RequstMessageResponse parseMessageResponse(InputStream is,
			final Tag tag, final int ID, final int N) throws Exception, SAXException {
        final RequstMessageResponse result = new RequstMessageResponse();        
        final DataWarpper data = new DataWarpper();
        
        RootElement tagElement = new RootElement("Tag");
     
        tagElement.setStartElementListener(new StartElementListener() {
			public void start(Attributes attributes) {
				String tagString = attributes.getValue("tag");
				data.msgReturnTag = tagString;
				assert(tagString != null);
				if (tag != null)
					assert(tagString.equals(formatQueryTag(tag)));
				data.cnout = Integer.valueOf(attributes.getValue("num"));
				result.N = data.cnout;
				result.returnTag = tagString;
				result.tag = tag;
				Log.i(TAG, "<Tag> start, tag = "+tagString+" num = "+data.cnout);
			}
        });
        tagElement.getChild("ID").setEndTextElementListener(
        	new EndTextElementListener(){
	            public void end(String body) {
	            	result.ID = Integer.valueOf(body);
	            	Log.i(TAG, "<ID> text = "+body);
	            }
        	}
        );
        
        Element msgElement = tagElement.getChild("Message");
        msgElement.requireChild("summary");
        msgElement.requireChild("title");
        msgElement.requireChild("link");
        msgElement.requireChild("time");
        
        msgElement.setElementListener(new ElementListener() {
			public void start(Attributes attributes) {
				data.msgID = Integer.valueOf(attributes.getValue("ID"));
			}
			public void end() {
				result.messages.add(new Message(
						data.msgID, data.msgSummary, data.msgLink,
						data.msgTime, data.msgTitle, 0, 0));			
			}
        });
  
        msgElement.getChild("summary").setEndTextElementListener(new EndTextElementListener(){
            public void end(String body) {
            	data.msgSummary = body;
            }
        });
        msgElement.getChild("title").setEndTextElementListener(new EndTextElementListener(){
            public void end(String body) {
            	data.msgTitle = body;
            }
        });
        msgElement.getChild("link").setEndTextElementListener(new EndTextElementListener(){
            public void end(String body) {
            	data.msgLink = body;
            }
        });
        msgElement.getChild("time").setEndTextElementListener(new EndTextElementListener(){
            public void end(String body) {
            	data.msgTime = body;
            }
        });
        
        Xml.parse(is, Xml.Encoding.UTF_8, tagElement.getContentHandler());
        return result;
}
	/**
	 * Query server about there are how many new messages belongs
	 * to a tag. As a side effort, server also return the newest
	 * message's ID.
	 * @param a list of tags that are wanted to query.
	 * @return a list of {@link MessageNumberResponse} as the result of 
	 * parsing the response XML
	 * @throws SAXException 
	 * @throws IOException 
	 * @see MessageNumberResponse 
	 */
	public ArrayList<MessageNumberResponse> getNewMessageNumber(ArrayList<Tag> tags) throws IOException, SAXException {
		if (tags == null) 
			return null;
		
		/*
		 *  Constructing post data.
		 *  When ask newest message ID, post data should likes like:
		 *  post[tag1] = tag.type + tag.sourse + "&" + tag.keyword
		 *  post[id1] = tag's newest id for now. i.e tag.latest_msg_id
		 *  post[tag2]
		 *  post[id2]
		 *  ........
		 */
		List<NameValuePair> data = new ArrayList<NameValuePair>(tags.size() * 2);
		int curr = 0;;
		for (curr = 0; curr < tags.size(); ++curr) {
			Tag tag = tags.get(curr);
			int id = tag.latest_msg_id;
			/*
			data.add(new BasicNameValuePair("tag" + String.valueOf(curr),
						formatQueryTag(tag)));
			data.add(new BasicNameValuePair("ID" + String.valueOf(curr),
					 						     String.valueOf(id)));
					 						     */
			data.add(new BasicNameValuePair("tag", formatQueryTag(tag)+"&"+id));
		}
        
		//InputStream is = postData(data, serverAddress + "messageNumber");
		InputStream is = postData(data, ChocolateSolver.getInstance().getServerAddress() + "getnum");
		return parseMessageNumber(is, tags);
		
	}
	
	/**
	 * Construct post data format `tagx' for a tag.
	 */
	private String formatQueryTag(Tag tag) {
		return tag.getTagIdentity();
		//return String.valueOf(tag.type) + tag.source + "&" + tag.keyword;
	}
	
	/**
	 * Parse the XML from server for responding new message query.
	 * @param is HTML response
	 * @param tags querying tag
	 * @return List of parsed response
	 * @throws SAXException 
	 * @throws IOException 
	 */
	public ArrayList<MessageNumberResponse> parseMessageNumber(InputStream is, 
			final ArrayList<Tag> tags) throws IOException, SAXException {
        final MessageNumberResponse currentMessage = new MessageNumberResponse();
        final HashSet<Tag> found = new HashSet<Tag>(tags.size());
        RootElement root = new RootElement("MsgNum");
        Element tagElement = root.getChild("Tag");
        int listsize = 16;
        if (tags != null) 
        	listsize = tags.size();
        final ArrayList<MessageNumberResponse> messages = 
        		new ArrayList<MessageNumberResponse>(listsize);
        //Element tagElement = root.getChild("Tag");
        
        tagElement.setElementListener(new ElementListener() {

			public void start(Attributes attributes) {
				String tagString = attributes.getValue("tag");
				assert(tagString != null);
				
	
				currentMessage.tag = null;
				currentMessage.returnTag = tagString;
				if (tags == null) 
					return ;
			
				Log.i(TAG, "returnTag: "+ currentMessage.returnTag);

				
				for (Tag tag: tags) {
					if (!found.contains(tag) && formatQueryTag(tag).equals(tagString)) {
						currentMessage.tag = tag;
						found.add(tag);
						Log.i(TAG, "Find out tag: "+currentMessage.tag.toString());
						return;
					}
				}
				// We should never reach here...
				// Always find a available tag
				Log.e(TAG, "Failed to find a tag");
				
				assert(false);
			}

			@Override
			public void end() {
                messages.add(currentMessage.copy());				
			}
        	
        });
        tagElement.requireChild("ID");
        tagElement.getChild("ID").setEndTextElementListener(new EndTextElementListener(){
            @Override
			public void end(String body) {
                currentMessage.newestID = Integer.valueOf(body);
            }
        });;
        tagElement.requireChild("newnum");
        tagElement.getChild("newnum").setEndTextElementListener(new EndTextElementListener(){
            @Override
			public void end(String body) {
                currentMessage.newMessageNumber = Integer.valueOf(body);
            }
        });
        Xml.parse(is, Xml.Encoding.UTF_8, root.getContentHandler());

        return messages;
	}
	
	private InputStream postData(List<NameValuePair> data, String httpAddr) throws ClientProtocolException, IOException {
	    // Create a new HttpClient and Post Header
	    HttpClient httpclient = new DefaultHttpClient();
	    HttpPost httppost = new HttpPost(httpAddr);
	    Log.i(TAG, "httpAddr = "+httpAddr);
		Log.i(TAG, "Posting data: "+data+"to "+httpAddr);
	    // Add data
		UrlEncodedFormEntity a = new UrlEncodedFormEntity(data, "UTF-8");
		Log.i(TAG, "url encoded: "+a.toString());
	    httppost.setEntity(new UrlEncodedFormEntity(data, "UTF-8"));
	    // Execute HTTP Post Request
	    HttpResponse response = httpclient.execute(httppost);
	    return response.getEntity().getContent();

	    
	}
}
